#define _CRT_SECURE_NO_WARNINGS 1



            《 Linux_20_自定义_Shell.cpp 》
 
 
 //      < man snprintf >

#include <stdio.h>

int printf(const char* format, ...);                          //按指定的格式 输出到显示器
int fprintf(FILE* stream, const char* format, ...);           //按指定的格式 输出到文件
int sprintf(char* str, const char* format, ...);              //按指定的格式 写到缓冲区
int snprintf(char* str, size_t size, const char* format, ...);//按指定的格式 按指定长度，写到缓冲区
 
————————————————————————————————————————————————————————————————


————————————————————————————————————————————————————————————————

 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>

#define SIZE 521
#define ZERO '\0'
char* gArgv[NUM];


//获取用户名
const char* GetUserName()
{
    const char* name = getenv("USER");
    if (name == NULL)
    {
        return "None";
    }
    return name;
}

//获取主机名
const char* GetHostName()
{
    const char* hostname = getenv("USER");
    if (hostname == NULL)
    {
        return "None";
    }
    return hostname;
}

//获取路径
const char* GetCwd()
{
    const char* cwd = getenv("PWD");
    if (cwd == NULL)
    {
        return "None";
    }
    return cwd;
}

————————————————————————————————————————————————————————————————

//commandline: output:是一个输出型参数
void MakeCommandLineAndPrint(char line[], size_t size)
{
 
    const char* username = GetUserName();
    const char* hostname = GetHostName();
    const char* cwd = GetCwd();
    snprintf(line, size, "[%s@%s %s]> ", username, hostname, cwd);
    printf("%s", line);
    fflush(stdout);
}

int main()
{
		//1.我们需要自己输出一个命令行
    char commandline[SIZE];
    MakeCommandLineAndPrint(commandline, sizeof(commandline));
    printf("%s", commandline);

		//2.获取用户命令字符串
	  char usercommand[SIZE];
	  char *s = fgets(usercommand, sizeof(usercommand), stdin);
	  if(s == NULL)
		//暂时设置为 1
		return 1;	
 
 //输出的时候 敲的 /n 也会被录入。 所以我们把输入字符串长度-1 的位置改为字符串的结束标志： \0 
     usercommand[strlen(usercommand)-1] = ZERO;		

	  printf("echo :%s\n", usercommand);
    sleep(5);
    return 0;
}

自定义_ Shell :
[用户名@主机名 路径]$
[a@192 j]$ ./myshell
[用户名 @ 主机名  路径               ]>		// > 自定义符号区分，# $ 。 
[a      @   a     /home/a/Desktop/a/j]>   [a@a /home/a/Desktop/a/j]> [a@192 j]$


————————————————————————————————————————————————————————————————————

 干净的命令2：

//commandline: output:是一个输出型参数
void MakeCommandLineAndPrint()
{
    char line[SIZE];
    const char* username = GetUserName();
    const char* hostname = GetHostName();
    const char* cwd = GetCwd();
    snprintf(line, sizeof(line), "[%s@%s %s]> ", username, hostname, cwd);
    printf("%s", line);
    fflush(stdout);
}

int main()
{
    //1.我们需要自己输出一个命令行
    MakeCommandLineAndPrint();

    //2.获取用户命令字符串
    char usercommand[SIZE];
    char* s = fgets(usercommand, sizeof(usercommand), stdin);
    if (s == NULL)
        //暂时设置为 1
        return 1;

    //输出的时候 敲的 /n 也会被录入。 所以我们把输入字符串长度-1 的位置改为字符串的
    结束标志： \0
        usercommand[strlen(usercommand) - 1] = ZERO;

    printf("echo :%s\n", usercommand);
    sleep(2);
    return 0;
}

 干净的命令：
 //自定义_ Shell :
[a@192 j]$ . / myshell
[用户名 @ 主机名  路径   
[a@a /home/a/Desktop/a/j]> [a@a/home/a/Desktop/a/j]> ls - a - l
echo : ls - a - l
[a@192 j]$


————————————————————————————————————————————————————————————————————


 3. 命令行分割

strtok(按指定字符串做分割符，并把标志置为 \0，下一次开始的地方 = +1) 
这里以空格做分割符， 
‘ ’， “ ” 注意区别 strtok：以字符串做分割。

————————————————————————————————————————————————————————————————————


————————————————————————————————————————————————————————————————————


          《 简单实现 Sell 功能 》


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>

#define SIZE 521
#define ZERO '\0'
#define SEP " "
#define NUM 32
#define SkipPath(p) do { p += strlen(p) -1; while(*p != '/') p--; } while(0) 

//为了方便，直接定义测试
char cwd[SIZE * 2];
char *gArgv[NUM];
int lastcode = 0;
 
void Die()
{
    exit(1);
}

const char* GetHome()
{
    const char* home = getenv("HOME");
    if (home == NULL) return "/";       //如果没有找到home(一般不会存在),直接转到根目录下。
    return home;
}

//获取用户名
const char* GetUserName()
{
    const char* name = getenv("USER");
    if (name == NULL)
    {
        return "None";
    }
    return name;
}

//获取主机名
const char* GetHostName()
{
    const char* hostname = getenv("USER");
    if (hostname == NULL)
    {
        return "None";
    }
    return hostname;
}

//获取路径
const char* GetCwd()
{
    const char* cwd = getenv("PWD");
    if (cwd == NULL)
    {
        return "None";
    }
    return cwd;
}

//commandline: output:是一个输出型参数
void MakeCommandLineAndPrint()
{
    char line[SIZE];
    const char* username = GetUserName();
    const char* hostname = GetHostName();
    const char* cwd = GetCwd();

    SkipPath(cwd);

    snprintf(line, sizeof(line), "[%s@%s %s]> ", username, hostname, strlen(cwd) == 1 ? "/" : cwd + 1); //cwd + 1 指向 '/' 的位置，让他跳过 '/'  // strlen(cwd) == 1 ? "/" : cwd + 1 特殊处理，只有 '/' (根目录)的时候。
    printf("%s", line);
    fflush(stdout);
}

int GetUsetCommand(char command[], size_t n)
{
    char* s = fgets(command, n, stdin);
    if (s == NULL)
        return -1;
    //输出的时候 敲的 /n 也会被录入。 
    //所以我们把输入字符串长度-1 的位置改为字符串的结束标志： \0 
    command[strlen(command) - 1] = ZERO;
    return strlen(command);
}


void SplitCommand(char command[], size_t n)
{
    (void)n;
    //"ls -a -l -n" ==> "ls" "-a" "-l" "-n"
    gArgv[0] = strtok(command, SEP);
    //strtok(按指定字符做分割符，并把标志置为 \0 ) 
    //这里以空格做分割符. 
    // ‘ ’， “ ” 注意：以字符串做分割。
    int index = 1;
    while ((gArgv[index++] = strtok(NULL, SEP)))
    {
        ;
    }     //done
           //故意写成 = ， 表示先赋值，再判断。
           //分割之后，strtok 会返回 NULL，刚好让gArgv最后一个元素是 NULL，
           //并且 while 判断结束。         
}

void ExecuteCommand()
{
    pid_t id = fork();
    if (id < 0)
        Die();
    else if (id == 0)
    {
        //child
        execvp(gArgv[0], gArgv);
        exit(errno);
    }
    else
    {
        //father
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if (rid > 0)
        {
            //TODO
            lastcode = WEXITSTATUS(status);
            if (lastcode != 0)
                printf("%s:%s:%d\n", gArgv[0], strerror(lastcode), lastcode);	//打印错误码错误信息
        }
    }
}

void Cd()
{
    const char* path = gArgv[1];
    if (path == NULL)
        path = GetHome();
    // path 一定存在
    chdir(path);        //更改路径
    
    //刷新环境变量
    char temp[SIZE * 2];
    //获取当前进程的 cwd 绝对路径
    getcwd(temp, sizeof(temp));     
    snprintf(cwd, sizeof(cwd), "PWD=%s", temp); //刷新环境变量
    putenv(cwd);        //？？直接给当前路径是不行的，需要绝对路径
}

int CheckBuildin()
{
    int yes = 0;
    const char* enter_cmd = gArgv[0];
    if (strcmp(enter_cmd, "cd") == 0)
    {
        yes = 1;    //如果是内建命令，yes 置为 1
        Cd();
    }
    else if (strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0)
    {
		  yes = 1;
        printf("%d\n", lastcode);
        lasetcode = 0;
    }

    return yes;
}

int main()
{
    int quit = 0;
    while (!quit)
    {
        //1.我们需要自己输出一个命令行
        MakeCommandLineAndPrint();

        //2.获取用户命令字符串
        char usercommand[SIZE];
        int n = GetUserCommand(usercommand, sizeof(usercommand));
        // 3. 命令行分割
        if (n <= 0)
            return 1;
        SplitCommand(usercommand, sizeof(usercommand));

        // 4.检测命令是否是 内建命令
        n = CheckBuildin();
        if (n)      
            continue;   //如果n 是内建命令，就继续执行下一条命令，如果不是内建命令，直接走后面的代码。
        // n. 
        ExecuteCommand();
    }
    return 0;
}

[a@a /home/a/Desktop/a/j]> ls -l --color 
Makefile
myshell.c

但是在切换路径时会出现问题： cd .. 
因为切换路径需要在 父进程 进行切换，你在子进程切换，所以影响不了想要切换的路径。

更改路径：
#include <unistd.h>
int chdir(const char* path);
int fchdir(int fd);

路径更新了，环境变量也要更新。（有个坑）

获取绝对路径：
#inc1ude <unistd.h >
char* getcwd(char* buf，size_t size);
char* getwd(char* buf);
char* get_current_dir_name(void);

总结：内建命令是由父进程自己执行的。

————————————————————————————————————————————————————————————————————

          < 宏函数 >

宏函数技巧：
 do { 如果有代码块，建议放到里面... } while (0) 如果后面跟 ‘;’ 也不会影响后续代码。类似一个函数。

宏函数：( \ 续行符 )
#define SkipPath(p) do { p += strlen(p) -1; while(*p != '/') p--; } while(0) 

//  #define SkipPath(p) do 
// { 
//   p = strlen(p) -1;
//   while(*p != '/') 
//      p--;     
// } while(0)
 
宏函数用来分割路径： /home/whb/111/code/lesson18/myshell 


————————————————————————————————————————————————————————————————————

C 语言 ：环境变量表
char** env = (char**)malloc(sizeof(char*) * 32);
